package cz.rosada.sx.library;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.InputStreamReader;

/**
 * FileUtils is a place for implementing various file utility functions usable throughout the
 * code.
 */
public class FileUtils {
    /**
     * Default buffer size used for file I/O operations.
     */
    public final static int FILE_BUFFER_SIZE = 1 << 16; // 64 KB

    /**
     * Creates folder and parents folders if needed.
     * 
     * @see File#mkdirs()
     * @param path specifies last sub-folder
     * @return true if success
     */
    public static boolean createDirs(final String path) {
        File file = new File(path);
        return file.mkdirs();
    }

    /**
     * Removes file extension i.e. C:\File.txt -> C:\File
     * 
     * @param fileName source file name
     * @return fileName without extension
     */
    public static String delFileExt(final String fileName) {
        String choppedFilename;

        int dotPlace = fileName.lastIndexOf('.');

        if (dotPlace >= 0) {
            choppedFilename = fileName.substring(0, dotPlace);
        } else {
            choppedFilename = fileName;
        }
        return choppedFilename;
    }

    /**
     * Returns file extensions (text after last dot).
     * 
     * @param fileName full path and filename or just filename.
     * @return i.e. "txt" or "TXT"
     */
    public static String extractFileExt(final String fileName) {
        String ext;

        int dotPlace = fileName.lastIndexOf('.');

        if (dotPlace >= 0) {
            ext = fileName.substring(dotPlace + 1);
        } else {
            ext = "";
        }
        return ext;
    }

/*    private boolean matchStream(final InputStream inputStreamA, final InputStream inputStreamB) 
 *         throws IOException {
        // vary BLOCK_SIZE to suit yourself.
        // it should probably a factor or multiple of the size of a disk sector/cluster.
        // Note that your max heap size may need to be adjusted
        //  if you have a very big block size or lots of these comparators.

        // assume inputStreamA and inputStreamB are streams from your two files.
        byte[] streamABlock = new byte[BLOCK_SIZE];
        byte[] streamBBlock = new byte[BLOCK_SIZE];
        boolean match;
        int bytesReadA;
        int bytesReadB;
        do {
            bytesReadA = inputStreamA.read(streamABlock);
            bytesReadB = inputStreamB.read(streamBBlock);
            match = ((bytesReadA == bytesReadB) && (bytesReadA == bytesReadB));
        } while (match && (bytesReadA > -1));
        return match;
    } */

    /*    /**
     * @param fileName1
     * @param fileName2
     * @return returns true if files are the same
     * @throws IOException
     */
/*    public boolean compareFiles(final String fileName1, final String fileName2) throws IOException {
        File f1 = new File(fileName1);
        File f2 = new File(fileName2);
        // compare file sizes
        if (f1.length() != f2.length())
            return false;
        InputStream inputStream1, inputStream2 = null;
//        try {
        // read and compare bytes pair-wise
        inputStream1 = new FileInputStream(f1);
        inputStream2 = new FileInputStream(f2);
        return matchStream(inputStream1, inputStream2);
        /*      } catch (FileNotFoundException e) {
                  e.printStackTrace();
                  return false;
              } catch (IOException e) {
                  e.printStackTrace();
                  return false;
              }
    }
    */

    /**
     * Delete file. Any problem is written to log. If file doesn't exist nothing is done.
     * 
     * @param file
     */
    public static void deleteFile(final File file) {
        if (!file.exists()) {
            return;
        }
        if (file.delete()) {
            // File successfully deleted.
            return;
        }

        new Thread("deleteFile") {
            public void run() {
                final long INITIAL_TIME = 10;
                final int MULTIPLICATOR = 10;
                final int RETRY_COUNT = 4;

                long time = INITIAL_TIME;
                for (int retry = 0; retry < RETRY_COUNT; retry++) {
                    try {
                        Thread.sleep(time);
                    } catch (InterruptedException e) {
//                        sLogger.error("sleep", e);
                    }
                    if (file.delete()) {
                        // File successfully deleted.
                        return;
                    }
                    time *= MULTIPLICATOR;
                }
//                sLogger.error("Can not delete file '" + file.getAbsolutePath() + "'.");
            }
        }.start();
    }

    /**
     * Deletes all files and subdirectories under dir.
     * 
     * @param dir directory or file
     */
    public static void deleteDirLater(final File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                deleteDirLater(new File(dir, children[i]));
            }
        }

        deleteFile(dir);
    }

    /**
     * Deletes all files and subdirectories under dir. If a deletion fails, the method stops
     * attempting to delete and returns false.
     * 
     * @param dir
     * @return Returns true if all deletions were successful.
     */
    public static boolean deleteDir(final File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }

        // The directory is now empty so delete it
        return dir.delete();
    }

    /**
     * Read a specified file and create a string with file contain.
     * 
     * @param filePath the name of the file to open. Not sure if it can accept URLs or just
     *            filenames. Path handling could be better, and buffer sizes are hardcoded
     * @return string with file contain
     * @throws java.io.IOException
     */
    public static String readFileAsString(final String filePath) throws java.io.IOException {
        StringBuffer fileData = new StringBuffer(FILE_BUFFER_SIZE);
        BufferedReader reader = new BufferedReader(new FileReader(filePath));
        try {
            char[] buf = new char[FILE_BUFFER_SIZE];
            int numRead = 0;
            while ((numRead = reader.read(buf)) != -1) {
                fileData.append(buf, 0, numRead);
            }
        } finally {
            reader.close();
        }
        return fileData.toString();
    }

    /**
     * Replace / in directory path to system separator and add file separator at the end in needed.
     * 
     * @param dirName
     * @return correct directory path
     */
    public static String correctDir(String dirName) {
        dirName = dirName.replace('/', File.separatorChar);
        if (!fileSeparator(dirName.charAt(dirName.length() - 1))) {
            dirName += File.separator;
        }

        return dirName;
    }

    /**
     * Return true if char is file path separator.
     * 
     * @param c
     * @return true if char is / or \
     */
    public static boolean fileSeparator(final char c) {
        return ((c == '/') || (c == '\\'));
    }

    /**
     * Replace environmental variable with i.e. %TEMP% -> C:\Temp
     * 
     * @param dir
     * @return path without environmental variables
     */
    public static String removeEnviromentVariables(String dir) {
        int i = 0;
        while (i < dir.length()) {
            i = dir.indexOf("%", i);
            if (i == -1) {
                break; // % doesn't exists.
            }

            int start = i;
            i++;
            i = dir.indexOf("%", i);

            String variable = dir.substring(start + 1, i);
            if (i == -1) {
                break; // Next % not found.
            }

            String value = System.getenv(variable);
            if (value != null) {
                dir = dir.substring(0, start) + value + dir.substring(i + 1, dir.length());
                i = start + value.length();
            }
        }
        return dir;
    }

    /**
     * @param expression
     * @return true if expression contain "*" or "?"
     */
    public static boolean isRegExpression(final String expression) {
        return expression.contains("*") || expression.contains("?");
    }

    /**
     * Accept files matched with regular expression.
     */
    public static class RegFilter implements FilenameFilter {
        private final String iRegExpression;

        /**
         * @param RegExpression do not forget to use
         *            {@link FileUtils#dosToRegExpression(String)} if ? or * is used.
         */
        public RegFilter(String RegExpression) {
            iRegExpression = RegExpression;
        }

        /**
         * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
         */
        public boolean accept(final File dir, final String name) {
            return (name.matches(iRegExpression));
        }
    }

    /**
     * Convert dos format regullar expresion (*, ?) to java match format (.*, .).
     * 
     * @param dosExpression dos formated regular expression
     * @return java match regular expression
     */
    public static String dosToRegExpression(final String dosExpression) {
        String result = dosExpression.replace(".", "\\.");
        result = result.replace("?", ".");
        result = result.replace("*", ".*");
        return result;
    }

    /**
     * @param dir
     * @return true if dir is a directory
     */
    public static boolean isDir(final String dir) {
        return (new File(dir).isDirectory());
    }

    /**
     * Select only directories.
     */
    public static class DirFilter implements FilenameFilter {

        /**
         * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
         */
        public boolean accept(final File dir, final String name) {
            return isDir(dir.getAbsolutePath() + File.separator + name);
        }
    }

    private final static char REPLACEMENT_CHAR = '_';

    /**
     * Transform string to valid file name.
     * 
     * @param invalidFileName any string that will be converted
     * @return valid file name
     */
    public static String legalFileName(final String invalidFileName) {
        StringBuffer result = new StringBuffer(legalFilePath(invalidFileName));

        int i = 0;
        int strLength = result.length();
        while (i < strLength) {
            if ((result.charAt(i) != ':') && (result.charAt(i) != '\\')) {
                i++;
            } else {
                result.setCharAt(i, REPLACEMENT_CHAR);
/*                result.delete(i, i + 1);
                strLength--;*/
            }
        }
        return result.toString();
    }

    /**
     * Transform string to valid file path (accept :, \).
     * 
     * @param invalidFilePath any string that will be converted
     * @return valid file path
     */
    public static String legalFilePath(final String invalidFilePath) {
        final int MAX_FILE_LENGTH = 63;
        StringBuffer result;

        if (invalidFilePath.length() == 0) {
            return "";
        } else if (invalidFilePath.length() > MAX_FILE_LENGTH) {
            result = new StringBuffer(invalidFilePath.substring(0, MAX_FILE_LENGTH));
        } else {
            result = new StringBuffer(invalidFilePath);
        }

        int i = 0;
        int strLength = result.length();
        while (i < strLength) {
            if (acceptFileNameChar(result.charAt(i))) {
                i++;
            } else {
                result.setCharAt(i, REPLACEMENT_CHAR);
/*                result.delete(i, i + 1);
                strLength--; */
            }
        }
        return result.toString();
    }

    private static boolean acceptFileNameChar(final char c) {
        if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9'))
                || (c == '_') || (c == '.') || (c == '-') || (c == ' ') || (c == 160) /* special space */
                || (c == '+') || (c == '=') || (c == '`') || (c == '~') || (c == '!') || (c == '@')
                || (c == '#') || (c == '$') || (c == '%') || (c == '^') || (c == '&') || (c == '(')
                || (c == ')') || (c == '{') || (c == '}') || (c == '\'') || (c == 180)
                || (c == ';') || (c == '[') || (c == ']') || (c == ',')) {
            return true;
        }
        return false;
    }

    /**
     * Execute command line and return stderr
     * 
     * @param commandLine command line to be execute
     * @return stderr output from process
     * @throws Exception
     */
    public static String executeCommand(final String commandLine) throws Exception {
        Process p = Runtime.getRuntime().exec(commandLine);
        StringBuffer lines = new StringBuffer();

        BufferedReader input = new BufferedReader(new InputStreamReader(p.getErrorStream()));
        try {
            String line;
            while ((line = input.readLine()) != null) {
                lines.append(line);
            }
            if (lines.length() > 0) {
                String errorMessage = commandLine + " stderr: " + lines;
                System.out.println(errorMessage);
            }
        } finally {
            input.close();
        }
        p.waitFor();
        return lines.toString();
    }

    /**
     * Find new not existing file.
     * 
     * @param inputPath
     * @return New file.
     */
    public static File newFileOrDir(final File inputPath) {
        String baseName;
        String extension;

        if (inputPath.getAbsolutePath().length() == 0) {
            return inputPath;
        }

        if (inputPath.isDirectory()) {
            baseName = inputPath.getAbsolutePath();
            extension = File.separator;
        } else {
            baseName = FileUtils.delFileExt(inputPath.getAbsolutePath());
            extension = "." + FileUtils.extractFileExt(inputPath.getAbsolutePath());
        }
        int i = 0;
        File outputPath = inputPath;
        while (i < Integer.MAX_VALUE) {
            if (i > 0) {
                outputPath = new File(baseName + "_" + Integer.valueOf(i).toString() + extension);
            }

            if (!outputPath.exists()) {
                return outputPath;
            }
            i++;
        }
        return null;
    }

    /**
     * Extract file name from the path. i.e. "C:\Test\File.txt" return "File.txt"
     * 
     * @param name
     * @return file name without path
     */
    public static String extractFileName(String name) {
        name = name.replace("\\", "/");
        int index = name.lastIndexOf("/");
        if (index >= 0) {
            return name.substring(index + 1);
        }
        return name;
    }

}
